package com.atguigu.study.aspect;

import com.atguigu.study.annotations.MyRedisCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Component
@Aspect
@Slf4j
public class MyRedisCacheAspect {//基于AOP实现自定义redis缓存切面，在方法执行前后进行缓存相关操作
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private RabbitTemplate rabbitTemplate;

// 定义切点 cachePointCut，表示所有被 @MyRedisCache 注解标记的方法，都会被该切面拦截，
// 执行后续的环绕通知逻辑。
    @Pointcut("@annotation(com.atguigu.study.annotations.MyRedisCache)")
    public void cachePointCut(){}

    @Around("cachePointCut()")//@Around 是环绕通知，会在目标方法执行前、执行后都插入逻辑。
    public Object doCache(ProceedingJoinPoint joinPoint){ //ProceedingJoinPoint
        // 用于获取目标方法的调用信息（如方法参数、方法签名等）
        Object result = null;

        try {

//        1.通过反射获取目标方法的方法签名和方法对象，后续用于解析方法上的注解、参数等。
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();//将方法存为对象

//        2.确定方法名后获得该方法上面配置的注解标签MyRedisCache
            MyRedisCache myRedisCacheAnnotation = method.getAnnotation(MyRedisCache.class);

//        3.拿到了MyRedisCache这个注解标签，解析注解中配置的 keyPrefix（缓存键前缀）和 matchValue
//        （Spring EL 表达式，用于动态拼接缓存键）。
            String keyPrefix = myRedisCacheAnnotation.keyPrefix();
            String matchValueSpringEL = myRedisCacheAnnotation.matchValue();

//        4.使用 SpelExpressionParser 解析 matchValue 中的 Spring EL 表达式
//        （如 #id，可动态获取方法参数值 ）
            ExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(matchValueSpringEL);
//        通过遍历方法参数，将参数名和值存入 EvaluationContext，为表达式解析提供变量上下文。
            EvaluationContext context = new StandardEvaluationContext();

//        5.使用反射获得方法里面的形参信息（这里形参只有一个id）
            Object[] args = joinPoint.getArgs();
            Parameter[] parameterNames = method.getParameters();
            for (int i = 0; i < parameterNames.length; i++) {
                System.out.println("获得方法里参数名和值：" + parameterNames[i].getName()
                        + "\t" + args[i].toString());
                context.setVariable(parameterNames[i].getName(), args[i].toString());
            }

//        6.通过上述，拼接redis的最终key形式
            String key = keyPrefix + ":" + expression.getValue(context).toString();
            System.out.println("----------拼接redis的最终key形式：" + key);

//        7.先去redis里面查看有没有键值对
            result = redisTemplate.opsForValue().get(key);
            if (result != null) {
                System.out.println("--------redis里面有，我直接返回结果，不看mysql：" + result);
                return result;
            }

//            8.如果redis里面没有，赵mysql查询
            result = joinPoint.proceed();//joinPoint.proceed() 触发目标方法执行
            // （如查询数据库获取用户信息），拿到方法返回结果。

//            9.mysql里查到了结果，要把结果存进redis里面补偿缓存
            if (result != null){
                System.out.println("------redis里面无，还需要把结果存入redis一次，缓存补偿:"+result);
                redisTemplate.opsForValue().set(key,result);
            }

        }catch (Throwable throwable){
            throwable.printStackTrace();
        }
        return result;

    }
}
